// i.d(t, { $: () => Label3D, N: () => O })
import * as THREE from "three"
import s from "../math/7170"
import TextGeometry from "./TextGeometry"

import Vectors from "./Vectors"
import c from "../math/27687"
import Logger from "../utils/Logger"
var a = {
  "0": "NONE",
  "1": "NUMERIC",
  "2": "ASCII",
  NONE: 0,
  NUMERIC: 1,
  ASCII: 2
}

import TextureLoader from "../loader/TextureLoader"

import ze from "../other/386-60386"
import BitmapFontAtlas from "../object/BitmapFontAtlas"
const f = new Logger("FontManager")
export class BitmapFontPackerStatic {
  name: string
  _atlas: BitmapFontAtlas
  mWidth: number
  constructor() {
    this.name = "roboto-700-42-static"
    this._atlas = new BitmapFontAtlas(this)
    this.mWidth = 0
  }
  async loadFont() {
    const [e] = await Promise.all([TextureLoader("./images/roboto-700-42_0.png")])
    const n = ze
    this.estimateWidth(n)
    this._atlas.update([e], n)
    return Promise.resolve({
      fontPath: this.name,
      font: this.name,
      packer: this as BitmapFontPackerStatic
    })
  }
  addChars(e) {
    f.debug(this.name, "does not support dynamic char adding")
  }
  measureText(e) {
    if (this.mWidth) return e.length * this.mWidth
    return false
  }
  atlas() {
    return this._atlas
  }
  estimateWidth(e: typeof ze) {
    const t = e.chars.find(e => e.id === "m".charCodeAt(0))
    t && (this.mWidth = t.width)
  }
}
import opentype from "opentype.js"

class Rectangle {
  oversized: boolean
  _rot: boolean
  _allowRotation: boolean
  _dirty: number
  _width: number
  _height: number
  _x: number
  _y: number
  _data: any
  tag: any
  hash: any
  constructor(e = 0, t = 0, i = 0, n = 0, s = !1, r?: boolean) {
    this.oversized = !1
    this._rot = !1
    this._allowRotation = null
    this._dirty = 0
    this._width = e
    this._height = t
    this._x = i
    this._y = n
    this._data = {}
    this._rot = s
    this._allowRotation = r
  }
  static Collide(e, t) {
    return e.collide(t)
  }
  static Contain(e, t) {
    return e.contain(t)
  }
  area() {
    return this.width * this.height
  }
  collide(e) {
    return e.x < this.x + this.width && e.x + e.width > this.x && e.y < this.y + this.height && e.y + e.height > this.y
  }
  contain(e) {
    return e.x >= this.x && e.y >= this.y && e.x + e.width <= this.x + this.width && e.y + e.height <= this.y + this.height
  }
  get width() {
    return this._width
  }
  set width(e) {
    e !== this._width && ((this._width = e), this._dirty++)
  }
  get height() {
    return this._height
  }
  set height(e) {
    e !== this._height && ((this._height = e), this._dirty++)
  }
  get x() {
    return this._x
  }
  set x(e) {
    e !== this._x && ((this._x = e), this._dirty++)
  }
  get y() {
    return this._y
  }
  set y(e) {
    e !== this._y && ((this._y = e), this._dirty++)
  }
  get rot() {
    return this._rot
  }
  set rot(e) {
    if (!1 !== this._allowRotation && this._rot !== e) {
      const t = this.width
      ;(this.width = this.height), (this.height = t), (this._rot = e), this._dirty++
    }
  }
  get allowRotation() {
    return this._allowRotation
  }
  set allowRotation(e: boolean) {
    this._allowRotation !== e && ((this._allowRotation = e), this._dirty++)
  }
  get data() {
    return this._data
  }
  set data(e: any) {
    if (!!e && e !== this._data) {
      this._data = e
      "object" == typeof e && e.hasOwnProperty("allowRotation") && (this._allowRotation = e.allowRotation)
      this._dirty++
    }
  }
  get dirty() {
    return this._dirty > 0
  }
  setDirty(e = !0) {
    this._dirty = e ? this._dirty + 1 : 0
  }
}
class Bin {
  _dirty: number
  rects: Rectangle[]
  constructor() {
    this._dirty = 0
  }
  get dirty() {
    return this._dirty > 0 || this.rects.some(e => e.dirty)
  }
  setDirty(e = !0) {
    this._dirty = e ? this._dirty + 1 : 0
    if (!e) for (let e of this.rects) e.setDirty && e.setDirty(!1)
  }
}
class MaxRectsBin extends Bin {
  maxWidth: number
  maxHeight: number
  padding: number
  freeRects: any[]
  verticalExpand: boolean
  options: any
  width: number
  height: number
  border: any
  stage: Rectangle
  tag: any
  data: boolean
  constructor(e = y, t = y, i = 0, n = {}) {
    super()
    this.maxWidth = e
    this.maxHeight = t
    this.padding = i
    this.freeRects = []
    this.rects = []
    this.verticalExpand = !1
    this.options = {
      smart: !0,
      pot: !0,
      square: !0,
      allowRotation: !1,
      tag: !1,
      exclusiveTag: !0,
      border: 0,
      logic: b.MAX_EDGE
    }
    this.options = Object.assign(Object.assign({}, this.options), n)
    this.width = this.options.smart ? 0 : e
    this.height = this.options.smart ? 0 : t
    this.border = this.options.border ? this.options.border : 0
    this.freeRects.push(
      new Rectangle(this.maxWidth + this.padding - 2 * this.border, this.maxHeight + this.padding - 2 * this.border, this.border, this.border)
    )
    this.stage = new Rectangle(this.width, this.height)
  }
  add(...e) {
    let t, i: Rectangle
    if (1 === e.length) {
      if ("object" != typeof e[0]) throw new Error("MacrectsBin.add(): Wrong parameters")
      i = e[0]
      let t = i.data && i.data.tag ? i.data.tag : i.tag ? i.tag : null
      if (this.options.tag && this.options.exclusiveTag && this.tag !== t) return
    } else {
      if (((t = e.length > 2 ? e[2] : null), this.options.tag && this.options.exclusiveTag)) {
        if (t && this.tag !== t.tag) return
        if (!t && this.tag) return
      }
      i = new Rectangle(e[0], e[1])
      i.data = t
      i.setDirty(!1)
    }
    const n = this.place(i)
    n && this.rects.push(n)
    return n
  }
  repack() {
    let e = []
    this.reset()
    this.rects.sort((e, t) => {
      const i = Math.max(t.width, t.height) - Math.max(e.width, e.height)
      return 0 === i && e.hash && t.hash ? (e.hash > t.hash ? -1 : 1) : i
    })
    for (let t of this.rects) this.place(t) || e.push(t)
    for (let t of e) this.rects.splice(this.rects.indexOf(t), 1)
    return e.length > 0 ? e : null
  }
  reset(e = !1, t = !1) {
    if (e) {
      this.data && delete this.data
      this.tag && delete this.tag
      this.rects = []
      if (t) {
        this.options = {
          smart: !0,
          pot: !0,
          square: !0,
          allowRotation: !1,
          tag: !1,
          border: 0
        }
      }
    }
    this.width = this.options.smart ? 0 : this.maxWidth
    this.height = this.options.smart ? 0 : this.maxHeight
    this.border = this.options.border ? this.options.border : 0
    this.freeRects = [new Rectangle(this.maxWidth + this.padding - 2 * this.border, this.maxHeight + this.padding - 2 * this.border, this.border, this.border)]
    this.stage = new Rectangle(this.width, this.height)
    this._dirty = 0
  }
  clone() {
    let e = new MaxRectsBin(this.maxWidth, this.maxHeight, this.padding, this.options)
    for (let t of this.rects) e.add(t)
    return e
  }
  place(e) {
    let t
    let i
    let n = e.data && e.data.tag ? e.data.tag : e.tag ? e.tag : undefined
    if (!this.options.tag || !this.options.exclusiveTag || this.tag === n) {
      i = e.hasOwnProperty("_allowRotation") && undefined !== e.allowRotation ? e.allowRotation : this.options.allowRotation
      t = this.findNode(e.width + this.padding, e.height + this.padding, i)
      if (t) {
        this.updateBinSize(t)
        let i = this.freeRects.length
        let n = 0
        for (; n < i; ) this.splitNode(this.freeRects[n], t) && (this.freeRects.splice(n, 1), i--, n--), n++
        this.pruneFreeList()
        this.verticalExpand = this.width > this.height
        e.x = t.x
        e.y = t.y
        e.rot === undefined && (e.rot = !1)
        e.rot = t.rot ? !e.rot : e.rot
        this._dirty++
        return e
      }
      if (this.verticalExpand) {
        if (
          this.updateBinSize(new Rectangle(e.width + this.padding, e.height + this.padding, this.border, this.height + this.padding - this.border)) ||
          this.updateBinSize(new Rectangle(e.width + this.padding, e.height + this.padding, this.width + this.padding - this.border, this.border))
        )
          return this.place(e)
      } else if (
        this.updateBinSize(new Rectangle(e.width + this.padding, e.height + this.padding, this.width + this.padding - this.border, this.border)) ||
        this.updateBinSize(new Rectangle(e.width + this.padding, e.height + this.padding, this.border, this.height + this.padding - this.border))
      )
        return this.place(e)
    }
  }
  findNode(e, t, i) {
    let n
    let s
    let r
    let o = Number.MAX_VALUE
    for (let a in this.freeRects) {
      s = this.freeRects[a]
      if (s.width >= e && s.height >= t) {
        n = this.options.logic === b.MAX_AREA ? s.width * s.height - e * t : Math.min(s.width - e, s.height - t)
        n < o && ((r = new Rectangle(e, t, s.x, s.y)), (o = n))
      }

      if (i && s.width >= t && s.height >= e) {
        n = this.options.logic === b.MAX_AREA ? s.width * s.height - t * e : Math.min(s.height - e, s.width - t)
        n < o && ((r = new Rectangle(t, e, s.x, s.y, !0)), (o = n))
      }
    }
    return r
  }
  splitNode(e, t) {
    if (!e.collide(t)) return !1
    if (t.x < e.x + e.width && t.x + t.width > e.x) {
      if (t.y > e.y && t.y < e.y + e.height) {
        let i = new Rectangle(e.width, t.y - e.y, e.x, e.y)
        this.freeRects.push(i)
      }
      if (t.y + t.height < e.y + e.height) {
        let i = new Rectangle(e.width, e.y + e.height - (t.y + t.height), e.x, t.y + t.height)
        this.freeRects.push(i)
      }
    }
    if (t.y < e.y + e.height && t.y + t.height > e.y) {
      if (t.x > e.x && t.x < e.x + e.width) {
        let i = new Rectangle(t.x - e.x, e.height, e.x, e.y)
        this.freeRects.push(i)
      }
      if (t.x + t.width < e.x + e.width) {
        let i = new Rectangle(e.x + e.width - (t.x + t.width), e.height, t.x + t.width, e.y)
        this.freeRects.push(i)
      }
    }
    return !0
  }
  pruneFreeList() {
    let e = 0
    let t = 0
    let i = this.freeRects.length
    for (; e < i; ) {
      t = e + 1
      let n = this.freeRects[e]
      for (; t < i; ) {
        let s = this.freeRects[t]
        if (s.contain(n)) {
          this.freeRects.splice(e, 1), e--, i--
          break
        }
        n.contain(s) && (this.freeRects.splice(t, 1), t--, i--)
        t++
      }
      e++
    }
  }
  updateBinSize(e) {
    if (!this.options.smart) return !1
    if (this.stage.contain(e)) return !1
    let t = Math.max(this.width, e.x + e.width - this.padding + this.border)
    let i = Math.max(this.height, e.y + e.height - this.padding + this.border)
    if (this.options.allowRotation) {
      const n = Math.max(this.width, e.x + e.height - this.padding + this.border),
        s = Math.max(this.height, e.y + e.width - this.padding + this.border)
      n * s < t * i && ((t = n), (i = s))
    }
    this.options.pot && ((t = Math.pow(2, Math.ceil(Math.log(t) * Math.LOG2E))), (i = Math.pow(2, Math.ceil(Math.log(i) * Math.LOG2E))))
    this.options.square && (t = i = Math.max(t, i))
    if (!(t > this.maxWidth + this.padding || i > this.maxHeight + this.padding)) {
      this.expandFreeRects(t + this.padding, i + this.padding)
      this.width = this.stage.width = t
      this.height = this.stage.height = i
      return !0
    }
  }
  expandFreeRects(e, t) {
    this.freeRects.forEach((i, n) => {
      i.x + i.width >= Math.min(this.width + this.padding - this.border, e) && (i.width = e - i.x - this.border),
        i.y + i.height >= Math.min(this.height + this.padding - this.border, t) && (i.height = t - i.y - this.border)
    }, this)
    this.freeRects.push(new Rectangle(e - this.width - this.padding, t - 2 * this.border, this.width + this.padding - this.border, this.border))
    this.freeRects.push(new Rectangle(e - 2 * this.border, t - this.height - this.padding, this.border, this.height + this.padding - this.border))
    this.freeRects = this.freeRects.filter(e => !(e.width <= 0 || e.height <= 0 || e.x < this.border || e.y < this.border))
    this.pruneFreeList()
  }
}
class OversizedElementBin extends Bin {
  width: number
  height: number
  data: any
  freeRects: Rectangle[]
  maxWidth: number
  maxHeight: number
  options: { smart: boolean; pot: boolean; square: boolean }
  tag: any
  constructor(...e: Rectangle[] | number[]) {
    super()
    this.rects = []
    if (1 === e.length) {
      if ("object" != typeof e[0]) throw new Error("OversizedElementBin: Wrong parameters")
      const t = e[0]
      this.rects = [t]
      this.width = t.width
      this.height = t.height
      this.data = t.data
      t.oversized = !0
    } else {
      this.width = Number(e[0])
      this.height = Number(e[1])
      this.data = e.length > 2 ? e[2] : null
      const t = new Rectangle(this.width, this.height)
      t.oversized = !0
      t.data = this.data
      this.rects.push(t)
    }
    this.freeRects = []
    this.maxWidth = this.width
    this.maxHeight = this.height
    this.options = { smart: !1, pot: !1, square: !1 }
  }
  add() {}
  reset(e = !1) {}
  repack() {}
  clone() {
    return new OversizedElementBin(this.rects[0])
  }
}
const y = 4096
var b = {
  "0": "MAX_AREA",
  "1": "MAX_EDGE",
  MAX_AREA: 0,
  MAX_EDGE: 1
}

class MaxRectsPacker {
  width: number
  height: number
  padding: number
  options: { smart: boolean; pot: boolean; square: boolean; allowRotation: boolean; tag: boolean; exclusiveTag: boolean; border: number; logic: any }
  _currentBinIndex: number
  bins: (OversizedElementBin | MaxRectsBin)[]
  constructor(e = 4096, t = 4096, i = 0, n = {}) {
    this.width = e
    this.height = t
    this.padding = i
    this.options = {
      smart: !0,
      pot: !0,
      square: !1,
      allowRotation: !1,
      tag: !1,
      exclusiveTag: !0,
      border: 0,
      logic: b.MAX_EDGE
    }
    this._currentBinIndex = 0
    this.bins = []
    this.options = Object.assign(Object.assign({}, this.options), n)
  }
  add(...e) {
    if (1 === e.length) {
      if ("object" != typeof e[0]) throw new Error("MacrectsPacker.add(): Wrong parameters")
      const t = e[0]
      if (t.width > this.width || t.height > this.height) this.bins.push(new OversizedElementBin(t))
      else {
        if (!this.bins.slice(this._currentBinIndex).find(e => undefined !== e.add(t))) {
          let e = new MaxRectsBin(this.width, this.height, this.padding, this.options),
            i = t.data && t.data.tag ? t.data.tag : t.tag ? t.tag : undefined
          this.options.tag && i && (e.tag = i), e.add(t), this.bins.push(e)
        }
      }
      return t
    }
    {
      const t = new Rectangle(e[0], e[1])
      if ((e.length > 2 && (t.data = e[2]), t.width > this.width || t.height > this.height)) this.bins.push(new OversizedElementBin(t))
      else {
        if (!this.bins.slice(this._currentBinIndex).find(e => undefined !== e.add(t))) {
          let e = new MaxRectsBin(this.width, this.height, this.padding, this.options)
          this.options.tag && t.data.tag && (e.tag = t.data.tag), e.add(t), this.bins.push(e)
        }
      }
      return t
    }
  }
  addArray(e) {
    if (!this.options.tag || this.options.exclusiveTag) this.sort(e, this.options.logic).forEach(e => this.add(e))
    else {
      if (0 === e.length) return
      let t
      e.sort((e, t) => {
        const i = e.data && e.data.tag ? e.data.tag : e.tag ? e.tag : undefined,
          n = t.data && t.data.tag ? t.data.tag : t.tag ? t.tag : undefined
        return undefined === n ? -1 : undefined === i ? 1 : n > i ? -1 : 1
      })
      let i = 0
      if (
        !this.bins.slice(this._currentBinIndex).find(n => {
          let s = n.clone()
          for (let r = i; r < e.length; r++) {
            const o = e[r],
              a = o.data && o.data.tag ? o.data.tag : o.tag ? o.tag : undefined
            if ((0 === r && (t = a), a !== t))
              return (t = a), this.sort(e.slice(i, r), this.options.logic).forEach(e => n.add(e)), (i = r), this.addArray(e.slice(r)), !0
            if (undefined === a) return this.sort(e.slice(r), this.options.logic).forEach(e => this.add(e)), (i = e.length), !0
            if (undefined === s.add(o)) return !1
          }
          return this.sort(e.slice(i), this.options.logic).forEach(e => n.add(e)), !0
        })
      ) {
        const t = e[i],
          n = new MaxRectsBin(this.width, this.height, this.padding, this.options),
          s = t.data && t.data.tag ? t.data.tag : t.tag ? t.tag : undefined
        this.options.tag && this.options.exclusiveTag && s && (n.tag = s), this.bins.push(n), this.addArray(e.slice(i))
      }
    }
  }
  reset() {
    ;(this.bins = []), (this._currentBinIndex = 0)
  }
  repack(e = !0) {
    if (e) {
      let e = []
      for (let t of this.bins)
        if (t.dirty) {
          let i = t.repack()
          i && e.push(...i)
        }
      return void this.addArray(e)
    }
    if (!this.dirty) return
    const t = this.rects
    this.reset(), this.addArray(t)
  }
  next() {
    return (this._currentBinIndex = this.bins.length), this._currentBinIndex
  }
  load(e) {
    e.forEach((e, t) => {
      if (e.maxWidth > this.width || e.maxHeight > this.height) this.bins.push(new OversizedElementBin(e.width, e.height, {}))
      else {
        let i = new MaxRectsBin(this.width, this.height, this.padding, e.options)
        i.freeRects.splice(0),
          e.freeRects.forEach((e, t) => {
            i.freeRects.push(new Rectangle(e.width, e.height, e.x, e.y))
          }),
          (i.width = e.width),
          (i.height = e.height),
          e.tag && (i.tag = e.tag),
          (this.bins[t] = i)
      }
    }, this)
  }
  save() {
    let e = []
    return (
      this.bins.forEach(t => {
        let i = {
          width: t.width,
          height: t.height,
          maxWidth: t.maxWidth,
          maxHeight: t.maxHeight,
          freeRects: [],
          rects: [],
          options: t.options
        }
        !!t.tag && (i = Object.assign(Object.assign({}, i), { tag: t.tag })),
          t.freeRects.forEach(e => {
            i.freeRects.push({
              x: e.x,
              y: e.y,
              width: e.width,
              height: e.height
            })
          }),
          e.push(i)
      }),
      e
    )
  }
  sort(e, t = b.MAX_EDGE) {
    return e.slice().sort((e, i) => {
      const n = t === b.MAX_EDGE ? Math.max(i.width, i.height) - Math.max(e.width, e.height) : i.width * i.height - e.width * e.height
      return 0 === n && e.hash && i.hash ? (e.hash > i.hash ? -1 : 1) : n
    })
  }
  get currentBinIndex() {
    return this._currentBinIndex
  }
  get dirty() {
    return this.bins.some(e => e.dirty)
  }
  get rects() {
    let e = []
    for (let t of this.bins) e.push(...t.rects)
    return e
  }
}
const S = (function () {
    const e = document.createElement("a")
    return (
      document.body.appendChild(e),
      (e.style.display = "none"),
      (t, i) => {
        const n = t.toDataURL()
        ;(e.href = n), (e.download = i), e.click()
      }
    )
  })(),
  E = (e, t) => {
    const i = document.createElement("a")
    i.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(e)), i.setAttribute("download", t), i.click()
  }
const w = new Logger("font-packer"),
  _ = {
    outlineOffset: Number(funcUtil.getUrlParams("outlineOffset", 0.2)),
    save: "1" === funcUtil.getUrlParams("exportfont", "0"),
    saveDefaultFont: "1" === funcUtil.getUrlParams("exportdefault", "0"),
    verboseChars: !0,
    verboseFont: !0,
    perf: {
      log: !1,
      marks: {
        rebuild: "rebuild",
        kern: "kerning",
        metrics: "metrics",
        pack: "pack",
        texture: "texture",
        bmfont: "load-bmfont"
      }
    }
  }

export class BitmapFontPacker {
  needsRebuild: boolean
  _atlas: BitmapFontAtlas
  _loading: boolean
  _loaded: boolean
  _addedChars: any[]
  charSet: Set<any>
  kerningsMap: {}
  measureMap: {}
  metricsMap: {}
  scale: number
  throttledRebuild: (...params: any[]) => void
  options: any
  font: any
  canvas: HTMLCanvasElement
  ctx: any
  packer: MaxRectsPacker
  fontFilename: string
  constructor(e, t) {
    ;(this.needsRebuild = !1),
      (this._atlas = new BitmapFontAtlas(this as BitmapFontPacker)),
      (this._loading = !1),
      (this._loaded = !1),
      (this._addedChars = []),
      (this.charSet = new Set()),
      (this.kerningsMap = {}),
      (this.measureMap = {}),
      (this.metricsMap = {}),
      (this.scale = 1),
      (this.throttledRebuild = funcUtil.throttle(() => {
        setTimeout(() => this.rebuild(), 0)
      }, 1500)),
      (this.options = e),
      t && (this.font = t)
    try {
      ;(this.canvas = document.createElement("canvas")), (this.canvas.width = this.options.width), (this.canvas.height = this.options.height)
      const e = this.canvas && this.canvas.getContext("2d")
      if (!e) throw Error("Unable to create canvas context for font-packer")
      ;(this.ctx = e), (this.ctx.fillStyle = "white"), (this.ctx.textBaseline = "alphabetic"), (this.ctx.textAlign = "left")
    } catch (e) {
      w.error("Failed to create canvas rendering context", e)
    }
    if (((this.packer = new MaxRectsPacker(this.options.width, this.options.height, 2 * this.options.texturePadding)), this.options.defaultCharset)) {
      ;(function (e = !1) {
        const t = new Set(Array.from("m0123456789_-+'\"` \t")),
          i = []
        e && i.push([32, 127])
        for (const e of i) for (let i = e[0]; i < e[1]; i++) t.add(String.fromCharCode(i))
        return t
      })(this.options.defaultCharset === a.ASCII || _.saveDefaultFont).forEach(e => this.charSet.add(e))
    }
  }
  get loaded() {
    return !this._loading && this._loaded
  }
  async loadFont() {
    let e
    _.perf.log && performance.mark(_.perf.marks.bmfont)
    const t = `${this.options.fontFamily} for BitmapFontPacker`
    if (!this.font) {
      ;(this._loading = !0),
        (e = new window["FontFace"](t, `url("${this.options.fontPath}") format("woff")`, {
          style: `${this.options.fontStyle}`,
          weight: `${this.options.fontWeight}`
        })),
        e.load()
      const i = e.loaded
      document["fonts"]["add"](e)
      const n = opentype.load(this.options.fontPath),
        [s] = await Promise.all([n, i])
      this.font = s
    }
    _.perf.log && performance.measure(_.perf.marks.bmfont, _.perf.marks.bmfont), (this.scale = this.options.fontSize / this.font.unitsPerEm)
    const i = `${this.options.fontWeight} ${this.options.fontSize}px "${t}"`
    this.ctx.font = i
    _.verboseFont &&
      w.debug({
        contextFontFace: i,
        bmfont: this.font,
        charset: this.charSet
      })
    this._loading = !1
    this.fontFilename =
      `${this.options.fontFamily.toLowerCase()}-${this.options.fontWeight}-${this.options.fontSize}` +
      (this.options.outline ? "-outline-" + this.options.outlineWidth : "")
    this.needsRebuild = !0
    this.throttledRebuild()
    return { fontPath: this.options.fontPath, font: this.font, packer: this as BitmapFontPacker }
  }
  addChars(e) {
    const t = this.charSet.size
    return (
      Array.from(e).forEach(e => {
        this.charSet.has(e) || this._addedChars.push(e), this.charSet.add(e)
      }),
      t !== this.charSet.size && ((this.needsRebuild = !0), this.throttledRebuild()),
      this.needsRebuild
    )
  }
  atlas() {
    return this._atlas
  }
  rebuild() {
    if (!this.font) return
    if (this.loaded && this._addedChars.length < 1) return void (this.needsRebuild = !1)
    _.verboseChars && (w.debug(`Added ${this._addedChars.length} glyphs: \n${this._addedChars}`), (this._addedChars.length = 0)),
      performance.mark(_.perf.marks.rebuild),
      _.perf.log && performance.mark(_.perf.marks.metrics)
    const e = this.getAllGlyphMetrics()
    _.perf.log && performance.measure(_.perf.marks.metrics, _.perf.marks.metrics), _.perf.log && performance.mark(_.perf.marks.pack)
    const t = this.pack(e)
    _.perf.log && performance.measure(_.perf.marks.pack, _.perf.marks.pack), _.perf.log && performance.mark(_.perf.marks.texture)
    const { chars: i, textures: n } = this.assembleTexture(t)
    _.perf.log && performance.measure(_.perf.marks.texture, _.perf.marks.texture), _.perf.log && performance.mark(_.perf.marks.kern)
    const s = this.getKerningPairs()
    _.perf.log && performance.measure(_.perf.marks.kern, _.perf.marks.kern)
    const r = this.assembleFont(i, s, n)
    performance.measure(_.perf.marks.rebuild, _.perf.marks.rebuild), (this._loaded = !0), this._atlas.update(n, r)
    const o = `Rebuilt atlas for ${this.charSet.size} chars with ${r.kernings.length} kerning pairs`
    _.perf.log
      ? (w.debug(
          o,
          performance.getEntriesByName(_.perf.marks.rebuild, "measure")[0],
          performance.getEntriesByType("measure").map(e => ({ name: e.name, duration: e.duration })),
          this.options
        ),
        Object.values(_.perf.marks).forEach(e => {
          performance.clearMarks(e), performance.clearMeasures(e)
        }))
      : (w.debug(o, performance.getEntriesByName(_.perf.marks.rebuild, "measure")[0]),
        performance.clearMarks(_.perf.marks.rebuild),
        performance.clearMeasures(_.perf.marks.rebuild))
  }
  pack(e) {
    return (this.packer.bins.length = 0), this.packer.addArray(e), this.packer.bins.reverse()
  }
  assembleTexture(e) {
    const t = [],
      i = e.map(
        (e, i) => (
          this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height),
          this.options.transparent && this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height),
          e.rects.forEach(e => {
            const n = e.data.charData,
              s = e.x - n.xoffset,
              r = e.y + n.height + n.ymin - this.options.texturePadding
            this.options.outline && this.options.outlineWidth > 0
              ? (function (e, t, i, n, s) {
                  const r = e.strokeStyle,
                    o = e.fillStyle,
                    a = e.lineJoin,
                    l = e.miterLimit
                  ;(e.lineJoin = "miter"),
                    (e.miterLimit = 2),
                    e.beginPath(),
                    (e.strokeStyle = "black"),
                    (e.lineWidth = s),
                    e.strokeText(t, i, n),
                    e.beginPath(),
                    (e.fillStyle = "white"),
                    e.fillText(t, i, n),
                    (e.strokeStyle = r),
                    (e.fillStyle = o),
                    (e.lineJoin = a),
                    (e.miterLimit = l)
                })(this.ctx, e.data.charData.char, s, r, this.options.outlineWidth)
              : this.ctx.fillText(e.data.charData.char, s, r),
              (n.x = e.x),
              (n.y = e.y),
              (n.page = i),
              t.push(e.data.charData)
          }),
          _.save && S(this.ctx.canvas, `${this.fontFilename}_${i}.png`),
          this.makeCanvasTexture(i, !1)
        )
      )
    return { chars: t, textures: i }
  }
  getKerningPairs() {
    const e = [],
      t = [...this.charSet.values()].map(e => this.font.charToGlyphIndex(e)).filter(e => e > 0)
    for (const i of t)
      for (const n of t) {
        const t = `${i},${n}`
        let s = this.kerningsMap[t]
        if (!s) {
          ;(s = {
            first: i,
            second: n,
            amount: this.font.getKerningValue(i, n) * this.scale
          }),
            (this.kerningsMap[t] = s)
        }
        s && 0 !== s.amount && e.push(s)
      }
    return e
  }
  assembleFont(e, t, i) {
    const n = this.font.tables.os2,
      s = this.font.tables.name.fullName,
      r = s[Object.getOwnPropertyNames(s)[0]],
      o = {
        pages: i.map((e, t) => `${this.fontFilename}_${t}.png`),
        chars: e,
        info: {
          face: r,
          size: this.options.fontSize,
          bold: 0,
          italic: 0,
          charset: [...this.charSet.values()],
          unicode: 1,
          stretchH: 100,
          smooth: 1,
          aa: 1,
          padding: [0, 0, 0, 0],
          spacing: [this.options.texturePadding, this.options.texturePadding],
          os2version: n.version
        },
        common: {
          lineHeight: (n.sTypoAscender - n.sTypoDescender + n.sTypoLineGap) * this.scale,
          base: this.font.ascender * this.scale,
          scaleW: this.options.width,
          scaleH: this.options.height,
          pages: this.packer.bins.length,
          packed: 0,
          alphaChnl: 0,
          redChnl: 0,
          greenChnl: 0,
          blueChnl: 0
        },
        kernings: t
      }
    return (
      _.verboseFont && w.debug({ bmFontOutput: o, kerningPairCount: t.length }),
      _.save && (E(JSON.stringify(o), `${this.fontFilename}.json`), E(JSON.stringify(this.options, undefined, 2), `${this.fontFilename}-pack-options.json`)),
      o
    )
  }
  getMetrics(e) {
    const t = 2 * this.options.texturePadding,
      i = this.scale
    let n, s, r, o, a, l
    const c = this.font.charToGlyph(e),
      h = 0 !== c.index
    if (h) {
      let t = this.measureMap[e]
      t || ((t = c.getMetrics()), (this.measureMap[e] = t)),
        (n = (t.xMax - t.xMin) * i),
        (s = (t.yMax - t.yMin) * i),
        (r = t.xMin * i),
        (o = t.yMin * i),
        (a = t.yMax * i),
        (l = c.advanceWidth * i)
    } else {
      const t = this.measureChar(e)
      ;(n = t.width), (s = t.height), (r = t.xMin), (o = t.yMin), (a = t.yMax), (l = n)
    }
    return {
      char: e,
      width: n + t,
      height: s + t,
      xmin: r,
      ymin: o,
      ymax: a,
      xadvance: l + (this.options.outline ? this.options.outlineWidth * _.outlineOffset : 0),
      supported: h
    }
  }
  measureText(e) {
    if (!this.loaded) return null
    let t = 0
    for (const i of Array.from(e)) {
      const { width: e } = this.measureChar(i)
      t += e
    }
    return t
  }
  measureChar(e) {
    let t = this.metricsMap[e]
    t || ((t = this.ctx.measureText(e)), (this.metricsMap[e] = t))
    const i = t.actualBoundingBoxLeft + t.actualBoundingBoxRight
    return {
      width: t.width || i,
      height: t.actualBoundingBoxAscent + t.actualBoundingBoxDescent,
      xMin: t.actualBoundingBoxLeft,
      xMax: t.actualBoundingBoxRight,
      yMin: -t.actualBoundingBoxDescent,
      yMax: t.actualBoundingBoxAscent
    }
  }
  getAllGlyphMetrics() {
    const e = [],
      t = this.font.ascender * this.scale,
      i = this.options.texturePadding
    for (const n of this.charSet.values()) {
      const s = this.getMetrics(n),
        r = {
          data: {
            charData: {
              char: n,
              id: n.charCodeAt(0),
              width: s.width,
              height: s.height,
              xoffset: s.xmin - i,
              yoffset: t - s.ymax - i,
              xadvance: s.xadvance,
              chnl: 15,
              ymin: s.ymin
            }
          },
          width: s.width,
          height: s.height
        }
      e.push(r)
    }
    return e
  }
  makeCanvasTexture(e, t) {
    if (t) {
      const e = new Uint8Array(this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height).data),
        t = new THREE.DataTexture(e, this.canvas.width, this.canvas.height, THREE.RGBAFormat)
      return (t.flipY = !0), t
    }
    const i = new THREE.Texture(this.ctx.canvas)
    return (i.name = `${this.ctx.font}_${e}`), (i.format = THREE.RGBAFormat), (i.needsUpdate = !0), i
  }
}
const M = new Logger("FontManager"),
  x = {
    fontPath: "fonts/roboto-700.woff",
    fontWeight: 700,
    fontFamily: "Roboto",
    fontStyle: "normal",
    fontSize: 60,
    width: 2048,
    height: 2048,
    transparent: !0,
    defaultCharset: a.ASCII,
    outline: !1,
    outlineWidth: 4,
    texturePadding: 6
  },
  A = "1" === funcUtil.getUrlParams("staticfont", "0")
class FontManager {
  defaultFontOptions: {
    fontPath: string
    fontWeight: number
    fontFamily: string
    fontStyle: string
    fontSize: number
    width: number
    height: number
    transparent: boolean
    defaultCharset: any
    outline: boolean
    outlineWidth: number
    texturePadding: number
  }
  packerMap: Map<any, any>
  optionsMap: Map<any, any>
  parsedFontMap: Map<any, any>
  static _instance: FontManager
  constructor() {
    ;(this.defaultFontOptions = x), (this.packerMap = new Map()), (this.optionsMap = new Map()), (this.parsedFontMap = new Map())
  }
  static get instance() {
    return FontManager._instance || (FontManager._instance = new FontManager()), FontManager._instance
  }
  getFontId(e = this.defaultFontOptions) {
    const t = Object.assign(Object.assign({}, this.defaultFontOptions), e),
      i = JSON.stringify(t)
    return this.optionsMap.set(i, t), i
  }
  getFont(e) {
    let t = this.packerMap.get(e)
    if ((!t && A && ((t = new BitmapFontPackerStatic()), t.loadFont(), this.packerMap.set(e, t)), !t)) {
      const i = this.optionsMap.get(e) || JSON.parse(e),
        n = this.parsedFontMap.get(i.fontPath)
      ;(t = new BitmapFontPacker(i, n)),
        this.packerMap.set(e, t),
        t.loadFont().then(t => {
          M.debug("font loaded", e, t.fontPath), this.parsedFontMap.set(t.fontPath, t.font)
        })
    }
    return t.atlas()
  }
  measureText(e, t) {
    const i = this.packerMap.get(e)
    if (i) return i.measureText(t)
  }
}
import D from "../math/69626"
import ae from "../shader/60948"
import be from "../shader/45576"
import funcUtil from "../utils/func.util"
import Label3DMesh from "../mesh/Label3DMesh"
import configConst from "../constant/config.const"
import { FontScaleType } from "../enum/font.enum"

export default class Label3D extends THREE.Object3D {
  config: any
  fontManager: any
  scaleType: string
  update: () => void
  textGeometry: any
  labelTextMesh: any
  unscaledWidth: any
  unscaledHeight: any
  labelBackgroundMesh: any
  aspect: number
  fontHeightToMeters: number
  _onGeomUpdate: any
  collider: any
  fontId: any
  constructor(e, t = FontManager.instance, r = FontScaleType.WORLD) {
    super(),
      (this.config = e),
      (this.fontManager = t),
      (this.scaleType = r),
      (this.update = () => {
        const e = this.resolvedConfig
        this.textGeometry.update(e), this.textGeometry.computeBoundingBox()
        const t = this.textGeometry.layout.lineHeight - this.textGeometry.layout.baseline
        ;(this.labelTextMesh.position.x = -0.5 * this.textGeometry.layout.width + t),
          (this.labelTextMesh.position.y = -0.5 * this.textGeometry.layout.height + t),
          (this.labelTextMesh.position.z = 0.25),
          (this.unscaledWidth = this.textGeometry.layout.width + this.config.backgroundBorderWidth),
          (this.unscaledHeight = this.textGeometry.layout.height + this.config.backgroundBorderHeight),
          (this.labelBackgroundMesh || this.config.backgroundAsCollider) && this.labelBackgroundMesh.scale.set(this.unscaledWidth, this.unscaledHeight, 0.01),
          (this.aspect = this.unscaledWidth / Math.max(this.unscaledHeight, 0.001)),
          (this.fontHeightToMeters = 1 / e.font.info.size),
          this.updateScale(),
          this._onGeomUpdate && this._onGeomUpdate()
      })
    const a = undefined !== this.config.opacity ? this.config.opacity : 1
    if (this.config.background || this.config.backgroundAsCollider) {
      ;(this.config.backgroundOpacity = undefined !== this.config.backgroundOpacity ? this.config.backgroundOpacity : 1),
        (this.config.backgroundOpacity = this.config.background ? this.config.backgroundOpacity : 0)
      const t = new THREE.BoxGeometry(1, 1, 0.01),
        i = new THREE.MeshBasicMaterial({
          color: e.backgroundColor,
          transparent: !0,
          depthTest: this.config.backgroundOpacity > 0 && !e.disableDepth,
          depthWrite: this.config.backgroundOpacity > 0 && !e.disableDepth,
          opacity: this.config.backgroundOpacity,
          stencilRef: 1,
          stencilFail: THREE.KeepStencilOp,
          stencilZFail: THREE.KeepStencilOp,
          stencilZPass: THREE.ReplaceStencilOp,
          stencilFunc: THREE.AlwaysStencilFunc,
          stencilWrite: !0
        })
      ;(this.labelBackgroundMesh = new this.config.backgroundColliderType(t, i)),
        (this.labelBackgroundMesh.position.z = -0.25),
        (this.labelBackgroundMesh.name = "Label Background"),
        (this.collider = this.labelBackgroundMesh),
        this.add(this.labelBackgroundMesh)
    }
    const c: any = {
        outline: this.config.outline,
        outlineWidth: this.config.outlineWidth
      },
      h = {
        uniforms: {
          map: {
            type: "t",
            value: (d = Object.assign(Object.assign({}, c), {
              color: e.color,
              opacity: a
            })).map
          },
          color: { type: "c", value: new THREE.Color(d.color) },
          opacity: { type: "f", value: d.opacity }
        },
        vertexShader: ae,
        fragmentShader: be
      }
    var d
    const u = new THREE.RawShaderMaterial({
      uniforms: h.uniforms,
      fragmentShader: h.fragmentShader,
      vertexShader: h.vertexShader,
      side: THREE.DoubleSide,
      transparent: !0,
      depthTest: !1,
      depthWrite: !1,
      opacity: a
    })
    ;(u.name = "Label Text"), (this.fontId = this.fontManager.getFontId(c))
    const p = this.fontManager.getFont(this.fontId)
    ;(u.uniforms.map.value = p.textures[0]),
      p.onChanged(() => {
        ;(u.uniforms.map.value = p.textures[0]), this.update()
      })
    const m = this.resolvedConfig,
      g = m.font
    ;(this.textGeometry = new TextGeometry(m)),
      (this.labelTextMesh = new Label3DMesh(this.textGeometry, u)),
      (this.labelTextMesh.name = "Label Text"),
      (this.labelTextMesh.renderOrder = 10)
    const f = new THREE.Quaternion().setFromAxisAngle(Vectors.FORWARD, s.Id(180)),
      v = new THREE.Quaternion().setFromAxisAngle(Vectors.UP, s.Id(180))
    this.labelTextMesh.quaternion.multiply(f).multiply(v),
      this.add(this.labelTextMesh),
      (this.name = "Label Container"),
      (this.fontHeightToMeters = 1 / g.info.size),
      (this.scaleFactor = e.scale || this.fontHeightToMeters),
      this.update()
  }
  get resolvedConfig() {
    let e
    if (undefined !== this.config.wordWrapWidth) {
      const t = this.fontManager.measureText(this.fontId, this.config.text)
      undefined !== t && t > this.config.wordWrapWidth && (e = this.config.wordWrapWidth)
    }
    const t = this.fontManager.getFont(this.fontId)
    return Object.assign(Object.assign({}, this.config), {
      font: t.fontData,
      texture: t.textures[0],
      width: e
    })
  }
  onGeomUpdate(e) {
    this._onGeomUpdate = e
  }
  updateScale() {
    const e = this.config.scale * this.fontHeightToMeters
    this.scale.set(e, e, e)
  }
  get text() {
    return this.config.text
  }
  set text(e) {
    e !== this.name && ((this.config.text = e), (this.name = e), this.fontManager.getFont(this.fontId).addCharsIfNeeded(e), this.update())
  }
  get mesh() {
    return this.labelTextMesh
  }
  get scaleFactor() {
    return this.config.scale
  }
  set scaleFactor(e) {
    this.scaleFactor !== e && ((this.config.scale = e), this.updateScale())
  }
  get opacity() {
    return undefined !== this.config.opacity ? this.config.opacity : 1
  }
  set opacity(e) {
    if (e !== this.config.opacity) {
      this.config.opacity = e
      const t = e > 0 && !this.config.disableDepth
      if (this.config.background) {
        const i = this.labelBackgroundMesh.material
        ;(i.opacity = Math.min(this.config.backgroundOpacity || 1, e)), (i.depthWrite = e > configConst.labelConfig.DEPTH_WRITE_THRESHOD), (i.depthTest = t)
      }
      const i = this.labelTextMesh.material
      ;(i.uniforms.opacity.value = e), (i.depthTest = t), (this.visible = e > 0)
    }
  }
  setColorHex(e) {
    const t = this.labelTextMesh.material.uniforms.color
    t.value.getHex() !== e && t.value.setHex(e)
  }
  setRenderLayer(e) {
    ;(this.labelTextMesh.layers.mask = e.mask), this.labelBackgroundMesh && (this.labelBackgroundMesh.layers.mask = e.mask)
  }
  setRenderOrder(e) {
    ;(this.renderOrder = e), (this.labelTextMesh.renderOrder = e), this.labelBackgroundMesh && (this.labelBackgroundMesh.renderOrder = e)
  }
  setPosition(e, t = e => e) {
    this.position.copy(t(e))
  }
  setOrientation(e, t = 0) {
    this.quaternion.copy(e), 0 !== t && this.rotateZ(-t * s.Ue)
  }
  scaleBillboard(e, t, i, n, s, o, a = P.SCALE_DEFAULT) {
    if (0 !== i.elements[15]) this.scaleFactor = 0.2 * a * n * (P.ORTHO_IDEAL_HEIGHT / s)
    else {
      const l = c.D_(this.position, e, t, i.asThreeMatrix4()),
        h = Math.abs(l.x)
      if (h < 1) {
        const t = D.mY(i, e, this.position, s, a),
          l = (funcUtil.getMax(o, 1, 2.5) + n) * P.SCALE_ASPECT,
          c = 1 + P.SCALE_NDC - h * P.SCALE_NDC - l,
          d = Math.max(Math.min((1 / t) * c, 3), 0.001)
        this.scaleType === FontScaleType.NDC ? (this.scaleFactor = d) : (this.scaleFactor = Math.min(d * P.NDC_MULT, a * P.SCALE_WORLD))
      } else this.scaleFactor = 0.001
    }
    this.updateScale()
  }
}
const P = {
  SCALE_DEFAULT: 0.1,
  SCALE_WORLD: Number(funcUtil.getUrlParams("scale_world", 4)),
  SCALE_NDC: 0.5,
  SCALE_ASPECT: 0.035,
  DEPTH_WRITE_THRESHOD: 0.15,
  ORTHO_IDEAL_HEIGHT: 1500,
  NDC_MULT: 1.15
}
